10x Home

Introduction

Welcome to the SIB Days 2020 - virtual conference Spatial Transcriptomics workshop by 10x genomics!

The purpose of this tutorial will be to walk users through some of the steps necessary to explore data produced by the 10x Genomics Visium Spatial Gene Expression Solution and the Spaceranger pipeline. We will investigate the data sets are all freely available from 10x Genomics.

Seurat Tutorial

Things to know about this workshop

  1. All files that will be used can be found at: /mnt/libs/shared_data/

[Terminal]

  1. Getting started with R and Visium data outside of Seurat at: https://support.10xgenomics.com/spatial-gene-expression/software/pipelines/latest/rkit
  2. Reference genome for all samples is GRCh38/mm10
  3. All 10x software including Spaceranger, and Loupe Browser, can be downloaded from the 10x Support Site

Exploring Visium Data with Seurat

Load our packages

library(Seurat)
library(ggplot2)
library(patchwork)
library(dplyr)
library(RColorBrewer)

Loading data in a Seurat object

Real Dataset for the tutorial

breast_cancer <- Load10X_Spatial(data.dir = "/mnt/libs/shared_data/human_breast_cancer_1/outs/",
                filename = "V1_Breast_Cancer_Block_A_Section_1_filtered_feature_bc_matrix.h5")

Same data just internal to 10x

breast_cancer <- Load10X_Spatial(data.dir = "/mnt/analysis/marsoc/pipestances/HWHTFDSXX/SPATIAL_RNA_COUNTER_PD/163086/HEAD/outs/", slice = "slice1")

There are a bunch of data sets hosted by the Satija lab in the Seurat Data Package.

Results

QC

Let’s have a look at some basic QC information. Keep in mind that most Seurat plots are ggplot object and can be manipulated as such.

Counts = UMI Features = Genes

plot1 <- VlnPlot(breast_cancer, features = "nCount_Spatial", pt.size = 0.1) + 
  ggtitle("UMI") +
  theme(axis.text.x = element_blank(), 
        axis.title.x = element_blank(), 
        legend.position = "right") +
  NoLegend()

plot2 <- VlnPlot(breast_cancer, features = "nFeature_Spatial", pt.size = 0.1) + 
  ggtitle("Genes") +
  theme(axis.text.x = element_blank(), 
        axis.title.x = element_blank(), 
        legend.position = "right") +
  NoLegend()

plot3 <- SpatialFeaturePlot(breast_cancer, features = "nCount_Spatial") + 
  theme(legend.position = "right")

plot4 <- SpatialFeaturePlot(breast_cancer, features = "nFeature_Spatial") +
  theme(legend.position = "right")

plot1 + plot2 + plot3  + plot4 + plot_layout(nrow = 2, ncol = 2)

Normalization

Spaceranger does normalization for clustering and DE but does not return that normalized matrix

Pre-normalization Raw UMI counts

SpatialFeaturePlot(breast_cancer, features = c("ERBB2", "CD8A"))

SE transform

  • This will take ~3-4 min.

Don’t worry about reachediteration limit warnings. See https://github.com/ChristophH/sctransform/issues/25 for discussion

Default assay will now be set to SCT

breast_cancer <- SCTransform(breast_cancer, assay = "Spatial", verbose = TRUE)
SpatialFeaturePlot(breast_cancer, features = c("ERBB2", "CD8A"))

From Seurat:

The default parameters in Seurat emphasize the visualization of molecular data. However, you can also adjust the size of the spots (and their transparency) to improve the visualization of the histology image, by changing the following parameters:

pt.size.factor- This will scale the size of the spots. Default is 1.6
alpha - minimum and maximum transparency. Default is c(1, 1).
Try setting to alpha c(0.1, 1), to downweight the transparency of points with lower expression
p1 <- SpatialFeaturePlot(breast_cancer, features = "TTR", pt.size.factor = 1)+ 
  theme(legend.position = "right") +
  ggtitle("Actual Spot Size")
p2 <- SpatialFeaturePlot(breast_cancer, features = "TTR")+ 
  theme(legend.position = "right") +
  ggtitle("Scaled Spot Size")
p1 + p2

Dimensionality reduction, clustering, and visualization

We can then proceed to run dimensionality reduction and clustering on the RNA expression data, using the same workflow as we use for scRNA-seq analysis.

Some of these processes can be parallized

library(future)
# check the current active plan
plan()
# change the current plan to access parallelization
plan("multiprocess", workers = 4)
plan()

The default UMAP calculation is performed with the R-based UWOT library However, you can run UMAP in python via reticulate library and umap-learn. We have found that for smaller data sets (<= 10k cells/spots) UWOT is great. For much larger data sets (100k + cells/spots) umap-learn can be a faster option.

breast_cancer <- RunPCA(breast_cancer, assay = "SCT", verbose = FALSE)
breast_cancer <- FindNeighbors(breast_cancer, reduction = "pca", dims = 1:30)
Computing nearest neighbor graph
Computing SNN
breast_cancer <- FindClusters(breast_cancer, verbose = FALSE)
breast_cancer <- RunUMAP(breast_cancer, reduction = "pca", dims = 1:30)
15:50:26 UMAP embedding parameters a = 0.9922 b = 1.112
15:50:26 Read 3822 rows and found 30 numeric columns
15:50:26 Using Annoy for neighbor search, n_neighbors = 30
15:50:26 Building Annoy index with metric = cosine, n_trees = 50
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
15:50:27 Writing NN index file to temp file /tmp/RtmpAPtqQw/fileac4dd6d472868
15:50:27 Searching Annoy index using 1 thread, search_k = 3000
15:50:28 Annoy recall = 100%
15:50:29 Commencing smooth kNN distance calibration using 1 thread
15:50:29 Initializing from normalized Laplacian + noise
15:50:32 Commencing optimization for 500 epochs, with 157848 positive edges
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
15:50:42 Optimization finished

Now let’s have a look at the clustering

DimPlot(breast_cancer, reduction = "umap", label = FALSE,) +
  labs(color = "Cluster")

p1 <- DimPlot(breast_cancer, reduction = "umap", label = TRUE) +
  labs(color = "Cluster")
p2 <- SpatialDimPlot(breast_cancer, label = TRUE, label.size = 3) +
  labs(fill = "Cluster")

p1 + p2 + plot_annotation(
  title = 'Clustering in UMAP and Tissue Space',
  caption = 'Processed by Spaceranger 1.1\nNormilization and Clustering by Seurat'
) + plot_layout(nrow = 1)

I don’t really like these colors so let’s change them

myPalette <- colorRampPalette(rev(brewer.pal(11, "Spectral")))
p1 <- DimPlot(breast_cancer, reduction = "umap", label = TRUE) +
  labs(color = "Cluster") + 
  scale_color_manual(values = c("#b2df8a","#e41a1c","#377eb8","#4daf4a","#ff7f00","gold", 
                               "#a65628", "#999999", "black", "pink", "purple", "brown",
                               "grey", "yellow", "green"))

p2 <- SpatialDimPlot(breast_cancer, label = TRUE, label.size = 3) +
  labs(fill = "Cluster")+ 
  scale_fill_manual(values = c("#b2df8a","#e41a1c","#377eb8","#4daf4a","#ff7f00","gold", 
                               "#a65628", "#999999", "black", "pink", "purple", "brown",
                               "grey", "yellow", "green"))

p1 + p2 + plot_annotation(
  title = 'Clustering in UMAP and Tissue Space',
  caption = 'Processed by Spaceranger 1.1\nNormilization and Clustering by Seurat'
) + plot_layout(nrow = 1)

If interested you can also now look at UMI and Gene counts per cluster as well

plot1 <- VlnPlot(breast_cancer, features = "nCount_Spatial", pt.size = 0.1) + 
  ggtitle("UMI") +
  scale_fill_manual(values = c("#b2df8a","#e41a1c","#377eb8","#4daf4a","#ff7f00","gold", 
                               "#a65628", "#999999", "black", "pink", "purple", "brown",
                               "grey", "yellow", "green"))+
  theme(axis.text.x = element_blank(), 
        axis.title.x = element_blank(), 
        legend.position = "right") +
  NoLegend()

plot2 <- VlnPlot(breast_cancer, features = "nFeature_Spatial", pt.size = 0.1) + 
  ggtitle("Genes") +
  scale_fill_manual(values = c("#b2df8a","#e41a1c","#377eb8","#4daf4a","#ff7f00","gold", 
                               "#a65628", "#999999", "black", "pink", "purple", "brown",
                               "grey", "yellow", "green"))+
  theme(axis.title.x = element_blank(), 
        legend.position = "right") +
  NoLegend()

plot1 + plot2

We can also look at these data interactively

LinkedDimPlot(breast_cancer)

Spatially variable features

First we’ll identify deferentially expressed genes.

Parallelization helps here too let’s make sure our plan is still intact

plan()
Error in plan() : could not find function "plan"

- call: plan("multiprocess", workers = 4) indicates that it is

Looks like we have some very DE genes for clusters 4 and 11

clarify what ident.1 = 4, ident.2 = 6 are for

Identify the most up-regulated and down-regulated genes

de_markers_up <- de_markers %>%
  tibble::rownames_to_column(var = "Gene") %>% 
  arrange(-avg_logFC)

de_markers_down <- de_markers %>%
  tibble::rownames_to_column(var = "Gene") %>% 
  arrange(avg_logFC)

Most up-regulated genes

SpatialFeaturePlot(object = breast_cancer, features = de_markers_up$Gene[1:10], alpha = c(0.1, 1), ncol = 3)

Most down-regulated genes

SpatialFeaturePlot(object = breast_cancer, features = de_markers_down$Gene[1:10], alpha = c(0.1, 1), ncol = 3)

what are the top variable features?

VariableFeatures(breast_cancer)[1:10]
 [1] "IGKC"   "IGHG3"  "IGLC2"  "MGP"    "ALB"    "IGHG1"  "CPB1"   "CRISP3" "IGLC3"  "IGHM"  

what are the top DE genes?

rownames(de_markers)[1:10]
 [1] "CRISP3"  "IGFBP5"  "SLITRK6" "H3F3A"   "IGFBP4"  "MALAT1"  "C3"      "KRT18"   "S100A11" "MGP"    

Spatially Variable Genes

So what about spatial enrichment?

Some methods 1. Trendsceek 2. Splotch 3. SPARK 4. SpatialDE + We have found this implementation not to be very effective. It’s also not under active development

Using the top 100 variable genes find spatially enriched ones. Note that in the Seurat Spatial Tutorial they use 1000 genes. You can also use all genes but that will take a long time. Using a calculation of Morans I can sometimes be a faster approach, especially if you are using parallization.

breast_cancer <- FindSpatiallyVariableFeatures(breast_cancer, 
                                               assay = "SCT", 
                                               slot = "scale.data", 
                                               features = VariableFeatures(breast_cancer)[1:100],
                                               selection.method = "markvariogram", verbose = TRUE)

Have a look at the spatially variable genes calculated by markvariogram ordered from most variable to least variable

SpatiallyVariableFeatures(breast_cancer, selection.method = "markvariogram", decreasing = TRUE)
  [1] "CRISP3"     "CXCL14"     "MGP"        "CPB1"       "COX6C"      "SLITRK6"    "TTLL12"     "CCND1"      "MALAT1"     "AGR2"      
 [11] "ALB"        "GFRA1"      "S100G"      "CSTA"       "DEGS1"      "TFF3"       "MT-ND1"     "IGLC2"      "MT-CO1"     "IGHG3"     
 [21] "CD74"       "C6orf141"   "S100A6"     "MT-ND2"     "TFF1"       "IGKC"       "IGHG1"      "APOE"       "ZNF350-AS1" "HLA-DRA"   
 [31] "AC087379.2" "IGHG4"      "C3"         "FCGR3B"     "TIMP1"      "LINC00645"  "IGHM"       "SCGB2A2"    "KRT14"      "IGLC3"     
 [41] "KRT17"      "LYZ"        "APOC1"      "SCGB1D2"    "IGHA1"      "C1QA"       "AEBP1"      "APOD"       "KRT5"       "MMP7"      
 [51] "CCL19"      "COL6A2"     "TAGLN"      "S100A9"     "IGHG2"      "COL1A2"     "DCN"        "SPP1"       "COL1A1"     "CGA"       
 [61] "VIM"        "IGFBP7"     "FN1"        "CCDC80"     "CXCL9"      "IGHA2"      "TRBC2"      "SFRP2"      "KRT6B"      "S100A2"    
 [71] "LUM"        "COL3A1"     "IGLC7"      "SAA1"       "CARTPT"     "COMP"       "JCHAIN"     "CST1"       "PTGDS"      "SFRP4"     
 [81] "CD79A"      "CCL21"      "FABP4"      "MUC19"      "ACKR1"      "POSTN"      "MMP9"       "S100A7"     "VWF"        "AQP1"      
 [91] "CLDN5"      "ACTA2"      "MS4A1"      "IGLL5"      "MYH11"      "CXCL10"     "IGHD"       "HBB"        "TPSB2"      "AC005224.3"
top.features_trendseq <- head(SpatiallyVariableFeatures(breast_cancer, selection.method = "markvariogram"), 8)
SpatialFeaturePlot(breast_cancer, features = top.features_trendseq, ncol = 4, alpha = c(0.1, 1))

Moran’s I implementation. For other spatial data types the x.cuts and y.cuts determines the grid that is laid over the tissue in the capture area. Here we’ll remove those

breast_cancer <- FindSpatiallyVariableFeatures(breast_cancer, 
                                               assay = "SCT", 
                                               slot = "scale.data", 
                                               features = VariableFeatures(breast_cancer)[1:100],
                                               selection.method = "moransi")
Computing Moran's I

Have a look at the spatially variable genes calculated by moransi ordered from most variable to least variable

SpatiallyVariableFeatures(breast_cancer, selection.method = "moransi", decreasing = TRUE)
  [1] "CRISP3"     "CXCL14"     "TTLL12"     "SLITRK6"    "GFRA1"      "CCND1"      "AGR2"       "COX6C"      "MGP"        "ALB"       
 [11] "MALAT1"     "CPB1"       "DEGS1"      "C6orf141"   "CSTA"       "MT-ND1"     "TFF3"       "MT-CO1"     "S100A6"     "MT-ND2"    
 [21] "LINC00645"  "FCGR3B"     "S100G"      "TFF1"       "C3"         "ZNF350-AS1" "HLA-DRA"    "CD74"       "SCGB1D2"    "APOD"      
 [31] "IGLC2"      "TIMP1"      "IGHG3"      "SCGB2A2"    "APOC1"      "KRT14"      "LYZ"        "CCDC80"     "APOE"       "TAGLN"     
 [41] "IGHG1"      "AC087379.2" "CCL19"      "SPP1"       "IGKC"       "S100A9"     "KRT17"      "IGHM"       "FN1"        "KRT5"      
 [51] "IGFBP7"     "C1QA"       "COL6A2"     "AQP1"       "CXCL9"      "ACKR1"      "CARTPT"     "DCN"        "AEBP1"      "IGLC3"     
 [61] "IGHG4"      "VIM"        "S100A2"     "IGHG2"      "MMP7"       "IGHA1"      "CCL21"      "KRT6B"      "TRBC2"      "COL1A2"    
 [71] "CGA"        "SFRP2"      "VWF"        "COL1A1"     "SAA1"       "MUC19"      "COL3A1"     "CLDN5"      "SFRP4"      "POSTN"     
 [81] "PTGDS"      "IGHA2"      "JCHAIN"     "CD79A"      "ACTA2"      "LUM"        "CST1"       "S100A7"     "COMP"       "MS4A1"     
 [91] "IGLC7"      "MMP9"       "HBB"        "FABP4"      "MYH11"      "IGLL5"      "CXCL10"     "TPSB2"      "IGHD"       "AC005224.3"
top.features_moransi <- head(SpatiallyVariableFeatures(breast_cancer, selection.method = "moransi"), 8)
SpatialFeaturePlot(breast_cancer, features = top.features_moransi, ncol = 4, alpha = c(0.1, 1))

We can see that the results are slightly different. So let’s take a look at why.

spatially_variable_genes <- breast_cancer@assays$SCT@meta.features %>%
  tidyr::drop_na()

spatially_variable_genes

You can see the two methods show

mm_cor <- cor.test(spatially_variable_genes$moransi.spatially.variable.rank, spatially_variable_genes$markvariogram.spatially.variable.rank)
ggplot(spatially_variable_genes, aes(x=moransi.spatially.variable.rank,y=markvariogram.spatially.variable.rank))+
  geom_point()+
  geom_smooth()+
  xlab("Morans I Rank")+
  ylab("Markvariogram Rank")+
  annotate("text", x = 25, y = 75, label = paste("Pearson's Correlation\n", round(mm_cor$estimate[1], digits = 2), sep = ""))+
  theme_bw()

Cancer annotations

Where are these genes being expressed relative to pathologist annotation?

ca <- readbitmap::read.bitmap("images/Breast Cancer Path.png")
# in the tutorial
# ca <- readbitmap::read.bitmap('/mnt/libs/shared_data/human_breast_cancer_1/images/Breast_Cancer_Path.png')
plot(0:1,0:1,type="n",ann=FALSE,axes=FALSE)
rasterImage(ca,0,0,1,1)

Looks like the Matrix Gla protein ( MGP ) gene is enriched in Ductal Carcinoma In Situ. Not a lot is known about MGP in the context of cancer but it looks like it could be an interesting novel gene to investigate with regard to Ductal Carcinoma In Situ.

SpatialFeaturePlot(object = breast_cancer, features = "MGP", alpha = c(0.1, 1), ncol = 3)

LS0tCnRpdGxlOiAiU0lCIERheXMgMjAyMCAtIFZpcnR1YWwgQ29uZmVyZW5jZSAtIEp1bmUgNCwgMjAyMCIKc3VidGl0bGU6ICJEYXRhc2V0OiBCcmVhc3QgQ2FuY2VyIgphdXRob3I6CiAgLSBQYXRyaWNrIFJvZWxsaSwgQ29tcHV0YXRpb25hbCBCaW9sb2dpc3QgMiAtIENvbXB1dGF0aW9uYWwgQmlvbG9neV5bMTB4IEdlbm9taWNzLCBwYXRyaWNrLnJvZWxsaUAxMHhnZW5vbWljcy5jb20gXQogIC0gU3RlZmFuaWEgR2lhY29tZWxsbywgQ29tcHV0YXRpb25hbCBCaW9sb2dpc3QgMiAtIENvbXB1dGF0aW9uYWwgQmlvbG9neV5bMTB4IEdlbm9taWNzLCBzdGVmYW5pYS5naWFjb21lbGxvQDEweGdlbm9taWNzLmNvbV0KICAtIFN0ZXBoZW4gV2lsbGlhbXMsIFNlbmlvciBTY2llbnRpc3QgLSBDb21wdXRhdGlvbmFsIEJpb2xvZ3leWzEweCBHZW5vbWljcywgc3RlcGhlbi53aWxsaWFtc0AxMHhnZW5vbWljcy5jb21dCmRhdGU6ICdMYXN0IENvbXBpbGVkOiBgciBmb3JtYXQoU3lzLkRhdGUoKSwgIiVCICVkLCAlWSIpYCcKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICBjb2RlX2ZvbGRpbmc6IG5vbmUKICAgIHRoZW1lOiBqb3VybmFsCiAgICB0b2M6IHllcwogICAgdG9jX2RlcHRoOiAzCiAgICB0b2NfZmxvYXQ6IHllcwotLS0KClshWzEweCBIb21lXShodHRwczovL2dpdGh1Yi5jb20vc3RlcGhlbndpbGxpYW1zMjIvU0lCXzIwMjBfV29ya3Nob3AvcmF3L21hc3Rlci9pbWFnZXMvMTB4JTIwaG9tZXBhZ2UucG5nKV0oaHR0cHM6Ly93d3cuMTB4Z2Vub21pY3MuY29tLykKCiMgKipJbnRyb2R1Y3Rpb24qKgoKV2VsY29tZSB0byB0aGUgKipTSUIgRGF5cyAyMDIwIC0gdmlydHVhbCBjb25mZXJlbmNlKiogU3BhdGlhbCBUcmFuc2NyaXB0b21pY3Mgd29ya3Nob3AgYnkgMTB4IGdlbm9taWNzIQoKVGhlIHB1cnBvc2Ugb2YgdGhpcyB0dXRvcmlhbCB3aWxsIGJlIHRvIHdhbGsgdXNlcnMgdGhyb3VnaCBzb21lIG9mIHRoZSBzdGVwcyBuZWNlc3NhcnkgdG8gZXhwbG9yZSBkYXRhIHByb2R1Y2VkIGJ5IHRoZSAxMHggR2Vub21pY3MgVmlzaXVtIFNwYXRpYWwgR2VuZSBFeHByZXNzaW9uIFNvbHV0aW9uIGFuZCB0aGUgW1NwYWNlcmFuZ2VyIHBpcGVsaW5lXShodHRwczovL3N1cHBvcnQuMTB4Z2Vub21pY3MuY29tL3NwYXRpYWwtZ2VuZS1leHByZXNzaW9uL3NvZnR3YXJlL3BpcGVsaW5lcy9sYXRlc3Qvd2hhdC1pcy1zcGFjZS1yYW5nZXIpLiBXZSB3aWxsIGludmVzdGlnYXRlIHRoZSBkYXRhIHNldHMgIGFyZSBhbGwgZnJlZWx5IGF2YWlsYWJsZSBmcm9tIFsxMHggR2Vub21pY3NdKGh0dHBzOi8vc3VwcG9ydC4xMHhnZW5vbWljcy5jb20vc3BhdGlhbC1nZW5lLWV4cHJlc3Npb24vZGF0YXNldHMpLgoKW1NldXJhdCBUdXRvcmlhbF0oaHR0cHM6Ly9zYXRpamFsYWIub3JnL3NldXJhdC92My4xL3NwYXRpYWxfdmlnbmV0dGUuaHRtbCkKCioqVGhpbmdzIHRvIGtub3cgYWJvdXQgdGhpcyB3b3Jrc2hvcCoqCgoxLiBBbGwgZmlsZXMgdGhhdCB3aWxsIGJlIHVzZWQgY2FuIGJlIGZvdW5kIGF0OiBgL21udC9saWJzL3NoYXJlZF9kYXRhL2AKClshW1Rlcm1pbmFsXShodHRwczovL2dpdGh1Yi5jb20vc3RlcGhlbndpbGxpYW1zMjIvU0lCXzIwMjBfV29ya3Nob3AvcmF3L21hc3Rlci9pbWFnZXMvVGVybWluYWwucG5nKV0KCjIuIEdldHRpbmcgc3RhcnRlZCB3aXRoIFIgYW5kIFZpc2l1bSBkYXRhIG91dHNpZGUgb2YgU2V1cmF0IGF0OiBodHRwczovL3N1cHBvcnQuMTB4Z2Vub21pY3MuY29tL3NwYXRpYWwtZ2VuZS1leHByZXNzaW9uL3NvZnR3YXJlL3BpcGVsaW5lcy9sYXRlc3QvcmtpdAozLiBSZWZlcmVuY2UgZ2Vub21lIGZvciBhbGwgc2FtcGxlcyBpcyBHUkNoMzgvbW0xMAo0LiBBbGwgMTB4IHNvZnR3YXJlIGluY2x1ZGluZyBbU3BhY2VyYW5nZXJdKGh0dHBzOi8vc3VwcG9ydC4xMHhnZW5vbWljcy5jb20vc3BhdGlhbC1nZW5lLWV4cHJlc3Npb24vc29mdHdhcmUvcGlwZWxpbmVzL2xhdGVzdC93aGF0LWlzLXNwYWNlLXJhbmdlciksIGFuZCBbTG91cGUgQnJvd3Nlcl0oaHR0cHM6Ly9zdXBwb3J0LjEweGdlbm9taWNzLmNvbS9zcGF0aWFsLWdlbmUtZXhwcmVzc2lvbi9zb2Z0d2FyZS92aXN1YWxpemF0aW9uL2xhdGVzdC93aGF0LWlzLWxvdXBlLWJyb3dzZXIpLCBjYW4gYmUgZG93bmxvYWRlZCBmcm9tIHRoZSBbMTB4IFN1cHBvcnQgU2l0ZV0oaHR0cHM6Ly9zdXBwb3J0LjEweGdlbm9taWNzLmNvbS8pIAoKCiMgKipFeHBsb3JpbmcgVmlzaXVtIERhdGEgd2l0aCBTZXVyYXQqKgoKIyMgTG9hZCBvdXIgcGFja2FnZXMKYGBge3IgTGlicmFyaWVzLCBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkoU2V1cmF0KQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkocGF0Y2h3b3JrKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KFJDb2xvckJyZXdlcikKYGBgCgojIyBMb2FkaW5nIGRhdGEgaW4gYSBTZXVyYXQgb2JqZWN0CgpSZWFsIERhdGFzZXQgZm9yIHRoZSB0dXRvcmlhbApgYGB7ciBldmFsPUZBTFNFfQpicmVhc3RfY2FuY2VyIDwtIExvYWQxMFhfU3BhdGlhbChkYXRhLmRpciA9ICIvbW50L2xpYnMvc2hhcmVkX2RhdGEvaHVtYW5fYnJlYXN0X2NhbmNlcl8xL291dHMvIiwKICAgICAgICAgICAgICAgIGZpbGVuYW1lID0gIlYxX0JyZWFzdF9DYW5jZXJfQmxvY2tfQV9TZWN0aW9uXzFfZmlsdGVyZWRfZmVhdHVyZV9iY19tYXRyaXguaDUiKQpgYGAKCgpTYW1lIGRhdGEganVzdCBpbnRlcm5hbCB0byAxMHgKYGBge3J9CmJyZWFzdF9jYW5jZXIgPC0gTG9hZDEwWF9TcGF0aWFsKGRhdGEuZGlyID0gIi9tbnQvYW5hbHlzaXMvbWFyc29jL3BpcGVzdGFuY2VzL0hXSFRGRFNYWC9TUEFUSUFMX1JOQV9DT1VOVEVSX1BELzE2MzA4Ni9IRUFEL291dHMvIiwgc2xpY2UgPSAic2xpY2UxIikKYGBgCgpUaGVyZSBhcmUgYSBidW5jaCBvZiBkYXRhIHNldHMgaG9zdGVkIGJ5IHRoZSBTYXRpamEgbGFiIGluIHRoZSBbU2V1cmF0IERhdGEgUGFja2FnZV0oaHR0cHM6Ly9naXRodWIuY29tL3NhdGlqYWxhYi9zZXVyYXQtZGF0YSkuCgojICoqUmVzdWx0cyoqCiMjIFFDCkxldCdzIGhhdmUgYSBsb29rIGF0IHNvbWUgYmFzaWMgUUMgaW5mb3JtYXRpb24uIEtlZXAgaW4gbWluZCB0aGF0IG1vc3QgU2V1cmF0IHBsb3RzIGFyZSBnZ3Bsb3Qgb2JqZWN0IGFuZCBjYW4gYmUgbWFuaXB1bGF0ZWQgYXMgc3VjaC4KCkNvdW50cyA9IFVNSQpGZWF0dXJlcyA9IEdlbmVzCmBgYHtyLCBmaWcud2lkdGg9MTAsZmlnLmhlaWdodD04LCB3YXJuaW5nPUZBTFNFfQpwbG90MSA8LSBWbG5QbG90KGJyZWFzdF9jYW5jZXIsIGZlYXR1cmVzID0gIm5Db3VudF9TcGF0aWFsIiwgcHQuc2l6ZSA9IDAuMSkgKyAKICBnZ3RpdGxlKCJVTUkiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksIAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gInJpZ2h0IikgKwogIE5vTGVnZW5kKCkKCnBsb3QyIDwtIFZsblBsb3QoYnJlYXN0X2NhbmNlciwgZmVhdHVyZXMgPSAibkZlYXR1cmVfU3BhdGlhbCIsIHB0LnNpemUgPSAwLjEpICsgCiAgZ2d0aXRsZSgiR2VuZXMiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksIAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gInJpZ2h0IikgKwogIE5vTGVnZW5kKCkKCnBsb3QzIDwtIFNwYXRpYWxGZWF0dXJlUGxvdChicmVhc3RfY2FuY2VyLCBmZWF0dXJlcyA9ICJuQ291bnRfU3BhdGlhbCIpICsgCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInJpZ2h0IikKCnBsb3Q0IDwtIFNwYXRpYWxGZWF0dXJlUGxvdChicmVhc3RfY2FuY2VyLCBmZWF0dXJlcyA9ICJuRmVhdHVyZV9TcGF0aWFsIikgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIpCgpwbG90MSArIHBsb3QyICsgcGxvdDMgICsgcGxvdDQgKyBwbG90X2xheW91dChucm93ID0gMiwgbmNvbCA9IDIpCmBgYAoKIyMgTm9ybWFsaXphdGlvbgoKU3BhY2VyYW5nZXIgZG9lcyBub3JtYWxpemF0aW9uIGZvciBjbHVzdGVyaW5nIGFuZCBERSBidXQgZG9lcyBub3QgcmV0dXJuIHRoYXQgbm9ybWFsaXplZCBtYXRyaXgKClByZS1ub3JtYWxpemF0aW9uIApSYXcgVU1JIGNvdW50cwpgYGB7ciwgZmlnLndpZHRoPTEwLCB3YXJuaW5nPUZBTFNFfQpTcGF0aWFsRmVhdHVyZVBsb3QoYnJlYXN0X2NhbmNlciwgZmVhdHVyZXMgPSBjKCJFUkJCMiIsICJDRDhBIikpCmBgYAoKIyMjIFNFIHRyYW5zZm9ybQoKKyAgVGhpcyB3aWxsIHRha2UgfjMtNCBtaW4uIAoKRG9uJ3Qgd29ycnkgYWJvdXQgYHJlYWNoZWRpdGVyYXRpb24gbGltaXRgIHdhcm5pbmdzLiBTZWUgaHR0cHM6Ly9naXRodWIuY29tL0NocmlzdG9waEgvc2N0cmFuc2Zvcm0vaXNzdWVzLzI1IGZvciBkaXNjdXNzaW9uCgpEZWZhdWx0IGFzc2F5IHdpbGwgbm93IGJlIHNldCB0byBTQ1QKYGBge3IsIHdhcm5pbmc9RkFMU0V9CmJyZWFzdF9jYW5jZXIgPC0gU0NUcmFuc2Zvcm0oYnJlYXN0X2NhbmNlciwgYXNzYXkgPSAiU3BhdGlhbCIsIHZlcmJvc2UgPSBUUlVFKQpgYGAKCmBgYHtyLCBmaWcud2lkdGg9MTAsIHdhcm5pbmc9RkFMU0V9ClNwYXRpYWxGZWF0dXJlUGxvdChicmVhc3RfY2FuY2VyLCBmZWF0dXJlcyA9IGMoIkVSQkIyIiwgIkNEOEEiKSkKYGBgCgoKRnJvbSBTZXVyYXQ6IAoKVGhlIGRlZmF1bHQgcGFyYW1ldGVycyBpbiBTZXVyYXQgZW1waGFzaXplIHRoZSB2aXN1YWxpemF0aW9uIG9mIG1vbGVjdWxhciBkYXRhLiBIb3dldmVyLCB5b3UgY2FuIGFsc28gYWRqdXN0IHRoZSBzaXplIG9mIHRoZSBzcG90cyAoYW5kIHRoZWlyIHRyYW5zcGFyZW5jeSkgdG8gaW1wcm92ZSB0aGUgdmlzdWFsaXphdGlvbiBvZiB0aGUgaGlzdG9sb2d5IGltYWdlLCBieSBjaGFuZ2luZyB0aGUgZm9sbG93aW5nIHBhcmFtZXRlcnM6CgogICAgcHQuc2l6ZS5mYWN0b3ItIFRoaXMgd2lsbCBzY2FsZSB0aGUgc2l6ZSBvZiB0aGUgc3BvdHMuIERlZmF1bHQgaXMgMS42CiAgICBhbHBoYSAtIG1pbmltdW0gYW5kIG1heGltdW0gdHJhbnNwYXJlbmN5LiBEZWZhdWx0IGlzIGMoMSwgMSkuCiAgICBUcnkgc2V0dGluZyB0byBhbHBoYSBjKDAuMSwgMSksIHRvIGRvd253ZWlnaHQgdGhlIHRyYW5zcGFyZW5jeSBvZiBwb2ludHMgd2l0aCBsb3dlciBleHByZXNzaW9uCgoKCmBgYHtyLCBmaWcud2lkdGg9MTAsIHdhcm5pbmc9RkFMU0V9CnAxIDwtIFNwYXRpYWxGZWF0dXJlUGxvdChicmVhc3RfY2FuY2VyLCBmZWF0dXJlcyA9ICJJR0ZCUDUiLCBwdC5zaXplLmZhY3RvciA9IDEpKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiKSArCiAgZ2d0aXRsZSgiQWN0dWFsIFNwb3QgU2l6ZSIpCnAyIDwtIFNwYXRpYWxGZWF0dXJlUGxvdChicmVhc3RfY2FuY2VyLCBmZWF0dXJlcyA9ICJJR0ZCUDUiKSsgCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInJpZ2h0IikgKwogIGdndGl0bGUoIlNjYWxlZCBTcG90IFNpemUiKQpwMSArIHAyCmBgYAoKIyMjIERpbWVuc2lvbmFsaXR5IHJlZHVjdGlvbiwgY2x1c3RlcmluZywgYW5kIHZpc3VhbGl6YXRpb24KCldlIGNhbiB0aGVuIHByb2NlZWQgdG8gcnVuIGRpbWVuc2lvbmFsaXR5IHJlZHVjdGlvbiBhbmQgY2x1c3RlcmluZyBvbiB0aGUgUk5BIGV4cHJlc3Npb24gZGF0YSwgdXNpbmcgdGhlIHNhbWUgd29ya2Zsb3cgYXMgd2UgdXNlIGZvciBzY1JOQS1zZXEgYW5hbHlzaXMuCgpTb21lIG9mIHRoZXNlIHByb2Nlc3NlcyBjYW4gYmUgcGFyYWxsaXplZAoKYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1UUlVFfQpsaWJyYXJ5KGZ1dHVyZSkKIyBjaGVjayB0aGUgY3VycmVudCBhY3RpdmUgcGxhbgpwbGFuKCkKYGBgCgpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPVRSVUV9CiMgY2hhbmdlIHRoZSBjdXJyZW50IHBsYW4gdG8gYWNjZXNzIHBhcmFsbGVsaXphdGlvbgpwbGFuKCJtdWx0aXByb2Nlc3MiLCB3b3JrZXJzID0gNCkKcGxhbigpCmBgYAoKClRoZSBkZWZhdWx0IFVNQVAgY2FsY3VsYXRpb24gaXMgcGVyZm9ybWVkIHdpdGggdGhlIFtSLWJhc2VkIFVXT1RdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy91d290L2luZGV4Lmh0bWwpIGxpYnJhcnkgSG93ZXZlciwgeW91IGNhbiBydW4gVU1BUCBpbiBweXRob24gdmlhIHJldGljdWxhdGUgbGlicmFyeSBhbmQgYHVtYXAtbGVhcm5gLiBXZSBoYXZlIGZvdW5kIHRoYXQgZm9yIHNtYWxsZXIgZGF0YSBzZXRzICg8PSAxMGsgY2VsbHMvc3BvdHMpIFVXT1QgaXMgZ3JlYXQuIEZvciBtdWNoIGxhcmdlciBkYXRhIHNldHMgKDEwMGsgKyBjZWxscy9zcG90cykgYHVtYXAtbGVhcm5gIGNhbiBiZSBhIGZhc3RlciBvcHRpb24uCgpgYGB7cn0KYnJlYXN0X2NhbmNlciA8LSBSdW5QQ0EoYnJlYXN0X2NhbmNlciwgYXNzYXkgPSAiU0NUIiwgdmVyYm9zZSA9IEZBTFNFKQpicmVhc3RfY2FuY2VyIDwtIEZpbmROZWlnaGJvcnMoYnJlYXN0X2NhbmNlciwgcmVkdWN0aW9uID0gInBjYSIsIGRpbXMgPSAxOjMwKQpicmVhc3RfY2FuY2VyIDwtIEZpbmRDbHVzdGVycyhicmVhc3RfY2FuY2VyLCB2ZXJib3NlID0gRkFMU0UpCmJyZWFzdF9jYW5jZXIgPC0gUnVuVU1BUChicmVhc3RfY2FuY2VyLCByZWR1Y3Rpb24gPSAicGNhIiwgZGltcyA9IDE6MzApCmBgYAoKTm93IGxldCdzIGhhdmUgYSBsb29rIGF0IHRoZSBjbHVzdGVyaW5nCgpgYGB7ciwgZmlnLndpZHRoPTh9CkRpbVBsb3QoYnJlYXN0X2NhbmNlciwgcmVkdWN0aW9uID0gInVtYXAiLCBsYWJlbCA9IEZBTFNFLCkgKwogIGxhYnMoY29sb3IgPSAiQ2x1c3RlciIpCmBgYAoKYGBge3IsIGZpZy53aWR0aD0xMCxmaWcuaGVpZ2h0PTEwLCB3YXJuaW5nPUZBTFNFfQpwMSA8LSBEaW1QbG90KGJyZWFzdF9jYW5jZXIsIHJlZHVjdGlvbiA9ICJ1bWFwIiwgbGFiZWwgPSBUUlVFKSArCiAgbGFicyhjb2xvciA9ICJDbHVzdGVyIikKcDIgPC0gU3BhdGlhbERpbVBsb3QoYnJlYXN0X2NhbmNlciwgbGFiZWwgPSBUUlVFLCBsYWJlbC5zaXplID0gMykgKwogIGxhYnMoZmlsbCA9ICJDbHVzdGVyIikKCnAxICsgcDIgKyBwbG90X2Fubm90YXRpb24oCiAgdGl0bGUgPSAnQ2x1c3RlcmluZyBpbiBVTUFQIGFuZCBUaXNzdWUgU3BhY2UnLAogIGNhcHRpb24gPSAnUHJvY2Vzc2VkIGJ5IFNwYWNlcmFuZ2VyIDEuMVxuTm9ybWlsaXphdGlvbiBhbmQgQ2x1c3RlcmluZyBieSBTZXVyYXQnCikgKyBwbG90X2xheW91dChucm93ID0gMSkKYGBgCgpJIGRvbid0IHJlYWxseSBsaWtlIHRoZXNlIGNvbG9ycyBzbyBsZXQncyBjaGFuZ2UgdGhlbQpgYGB7cn0KbXlQYWxldHRlIDwtIGNvbG9yUmFtcFBhbGV0dGUocmV2KGJyZXdlci5wYWwoMTEsICJTcGVjdHJhbCIpKSkKYGBgCgpgYGB7ciwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTEwLCB3YXJuaW5nPUZBTFNFfQpwMSA8LSBEaW1QbG90KGJyZWFzdF9jYW5jZXIsIHJlZHVjdGlvbiA9ICJ1bWFwIiwgbGFiZWwgPSBUUlVFKSArCiAgbGFicyhjb2xvciA9ICJDbHVzdGVyIikgKyAKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYygiI2IyZGY4YSIsIiNlNDFhMWMiLCIjMzc3ZWI4IiwiIzRkYWY0YSIsIiNmZjdmMDAiLCJnb2xkIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiI2E2NTYyOCIsICIjOTk5OTk5IiwgImJsYWNrIiwgInBpbmsiLCAicHVycGxlIiwgImJyb3duIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJncmV5IiwgInllbGxvdyIsICJncmVlbiIpKQoKcDIgPC0gU3BhdGlhbERpbVBsb3QoYnJlYXN0X2NhbmNlciwgbGFiZWwgPSBUUlVFLCBsYWJlbC5zaXplID0gMykgKwogIGxhYnMoZmlsbCA9ICJDbHVzdGVyIikrIAogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoIiNiMmRmOGEiLCIjZTQxYTFjIiwiIzM3N2ViOCIsIiM0ZGFmNGEiLCIjZmY3ZjAwIiwiZ29sZCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIiNhNjU2MjgiLCAiIzk5OTk5OSIsICJibGFjayIsICJwaW5rIiwgInB1cnBsZSIsICJicm93biIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiZ3JleSIsICJ5ZWxsb3ciLCAiZ3JlZW4iKSkKCnAxICsgcDIgKyBwbG90X2Fubm90YXRpb24oCiAgdGl0bGUgPSAnQ2x1c3RlcmluZyBpbiBVTUFQIGFuZCBUaXNzdWUgU3BhY2UnLAogIGNhcHRpb24gPSAnUHJvY2Vzc2VkIGJ5IFNwYWNlcmFuZ2VyIDEuMVxuTm9ybWlsaXphdGlvbiBhbmQgQ2x1c3RlcmluZyBieSBTZXVyYXQnCikgKyBwbG90X2xheW91dChucm93ID0gMSkKYGBgCgpJZiBpbnRlcmVzdGVkIHlvdSBjYW4gYWxzbyBub3cgbG9vayBhdCBVTUkgYW5kIEdlbmUgY291bnRzIHBlciBjbHVzdGVyIGFzIHdlbGwKYGBge3J9CnBsb3QxIDwtIFZsblBsb3QoYnJlYXN0X2NhbmNlciwgZmVhdHVyZXMgPSAibkNvdW50X1NwYXRpYWwiLCBwdC5zaXplID0gMC4xKSArIAogIGdndGl0bGUoIlVNSSIpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCIjYjJkZjhhIiwiI2U0MWExYyIsIiMzNzdlYjgiLCIjNGRhZjRhIiwiI2ZmN2YwMCIsImdvbGQiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICIjYTY1NjI4IiwgIiM5OTk5OTkiLCAiYmxhY2siLCAicGluayIsICJwdXJwbGUiLCAiYnJvd24iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImdyZXkiLCAieWVsbG93IiwgImdyZWVuIikpKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksIAogICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIpICsKICBOb0xlZ2VuZCgpCgpwbG90MiA8LSBWbG5QbG90KGJyZWFzdF9jYW5jZXIsIGZlYXR1cmVzID0gIm5GZWF0dXJlX1NwYXRpYWwiLCBwdC5zaXplID0gMC4xKSArIAogIGdndGl0bGUoIkdlbmVzIikgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoIiNiMmRmOGEiLCIjZTQxYTFjIiwiIzM3N2ViOCIsIiM0ZGFmNGEiLCIjZmY3ZjAwIiwiZ29sZCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIiNhNjU2MjgiLCAiIzk5OTk5OSIsICJibGFjayIsICJwaW5rIiwgInB1cnBsZSIsICJicm93biIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiZ3JleSIsICJ5ZWxsb3ciLCAiZ3JlZW4iKSkrCiAgdGhlbWUoYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgICBsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiKSArCiAgTm9MZWdlbmQoKQoKcGxvdDEgKyBwbG90MgpgYGAKCgpXZSBjYW4gYWxzbyBsb29rIGF0IHRoZXNlIGRhdGEgaW50ZXJhY3RpdmVseQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPVRSVUV9CkxpbmtlZERpbVBsb3QoYnJlYXN0X2NhbmNlcikKYGBgCgojIyBTcGF0aWFsbHkgdmFyaWFibGUgZmVhdHVyZXMKCkZpcnN0IHdlJ2xsIGlkZW50aWZ5IGRlZmVyZW50aWFsbHkgZXhwcmVzc2VkIGdlbmVzLiAKClBhcmFsbGVsaXphdGlvbiBoZWxwcyBoZXJlIHRvbyBsZXQncyBtYWtlIHN1cmUgb3VyIHBsYW4gaXMgc3RpbGwgaW50YWN0CgpgYGB7ciwgZXZhbD1GQUxFLCBpbmNsdWRlPVRSVUV9CnBsYW4oKQpgYGAKYC0gY2FsbDogcGxhbigibXVsdGlwcm9jZXNzIiwgd29ya2VycyA9IDQpYCBpbmRpY2F0ZXMgdGhhdCBpdCBpcwoKTG9va3MgbGlrZSB3ZSBoYXZlIHNvbWUgdmVyeSBERSBnZW5lcyBmb3IgY2x1c3RlcnMgNCBhbmQgMTEKCgpjbGFyaWZ5IHdoYXQgaWRlbnQuMSA9IDQsIGlkZW50LjIgPSA2IGFyZSBmb3IKYGBge3IsIGZpZy53aWR0aD0xMCwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KZGVfbWFya2VycyA8LSBGaW5kTWFya2VycyhicmVhc3RfY2FuY2VyLCBpZGVudC4xID0gNCwgaWRlbnQuMiA9IDYpCmRlX21hcmtlcnMKYGBgCgojIyBJZGVudGlmeSB0aGUgbW9zdCB1cC1yZWd1bGF0ZWQgYW5kIGRvd24tcmVndWxhdGVkIGdlbmVzCmBgYHtyfQpkZV9tYXJrZXJzX3VwIDwtIGRlX21hcmtlcnMgJT4lCiAgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4odmFyID0gIkdlbmUiKSAlPiUgCiAgYXJyYW5nZSgtYXZnX2xvZ0ZDKQoKZGVfbWFya2Vyc19kb3duIDwtIGRlX21hcmtlcnMgJT4lCiAgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4odmFyID0gIkdlbmUiKSAlPiUgCiAgYXJyYW5nZShhdmdfbG9nRkMpCmBgYAoKCiMjIyBNb3N0IHVwLXJlZ3VsYXRlZCBnZW5lcwpgYGB7ciwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTEwLCB3YXJuaW5nPUZBTFNFfQpTcGF0aWFsRmVhdHVyZVBsb3Qob2JqZWN0ID0gYnJlYXN0X2NhbmNlciwgZmVhdHVyZXMgPSBkZV9tYXJrZXJzX3VwJEdlbmVbMToxMF0sIGFscGhhID0gYygwLjEsIDEpLCBuY29sID0gMykKYGBgCgojIyMgTW9zdCBkb3duLXJlZ3VsYXRlZCBnZW5lcwpgYGB7ciwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTEwLCB3YXJuaW5nPUZBTFNFfQpTcGF0aWFsRmVhdHVyZVBsb3Qob2JqZWN0ID0gYnJlYXN0X2NhbmNlciwgZmVhdHVyZXMgPSBkZV9tYXJrZXJzX2Rvd24kR2VuZVsxOjEwXSwgYWxwaGEgPSBjKDAuMSwgMSksIG5jb2wgPSAzKQpgYGAKCndoYXQgYXJlIHRoZSB0b3AgdmFyaWFibGUgZmVhdHVyZXM/CmBgYHtyfQpWYXJpYWJsZUZlYXR1cmVzKGJyZWFzdF9jYW5jZXIpWzE6MTBdCmBgYAoKd2hhdCBhcmUgdGhlIHRvcCBERSBnZW5lcz8KYGBge3J9CnJvd25hbWVzKGRlX21hcmtlcnMpWzE6MTBdCmBgYAoKIyMgU3BhdGlhbGx5IFZhcmlhYmxlIEdlbmVzCgpTbyB3aGF0IGFib3V0IHNwYXRpYWwgZW5yaWNobWVudD8gCgpTb21lIG1ldGhvZHMKMS4gW1RyZW5kc2NlZWtdKGh0dHBzOi8vd3d3Lm5hdHVyZS5jb20vYXJ0aWNsZXMvbm1ldGguNDYzNCkKMi4gW1NwbG90Y2hdKGh0dHBzOi8vd3d3LmJpb3J4aXYub3JnL2NvbnRlbnQvMTAuMTEwMS83NTcwOTZ2MSkKMy4gW1NQQVJLXShodHRwczovL3d3dy5uYXR1cmUuY29tL2FydGljbGVzL3M0MTU5Mi0wMTktMDcwMS03KQo0LiBbU3BhdGlhbERFXShodHRwczovL3d3dy5uYXR1cmUuY29tL2FydGljbGVzL25tZXRoLjQ2MzYpCiAgKyBXZSBoYXZlIGZvdW5kIHRoaXMgaW1wbGVtZW50YXRpb24gbm90IHRvIGJlIHZlcnkgZWZmZWN0aXZlLiBJdCdzIGFsc28gbm90IHVuZGVyIGFjdGl2ZSBkZXZlbG9wbWVudAoKClVzaW5nIHRoZSB0b3AgMTAwIHZhcmlhYmxlIGdlbmVzIGZpbmQgc3BhdGlhbGx5IGVucmljaGVkIG9uZXMuIE5vdGUgdGhhdCBpbiB0aGUgU2V1cmF0IFNwYXRpYWwgVHV0b3JpYWwgdGhleSB1c2UgMTAwMCBnZW5lcy4gWW91IGNhbiBhbHNvIHVzZSBhbGwgZ2VuZXMgYnV0IHRoYXQgd2lsbCB0YWtlIGEgbG9uZyB0aW1lLiBVc2luZyBhIGNhbGN1bGF0aW9uIG9mIFtNb3JhbnMgSV0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvTW9yYW4lMjdzX0kpIGNhbiBzb21ldGltZXMgYmUgYSBmYXN0ZXIgYXBwcm9hY2gsIGVzcGVjaWFsbHkgaWYgeW91IGFyZSB1c2luZyBwYXJhbGxpemF0aW9uLgoKCmBgYHtyfQpicmVhc3RfY2FuY2VyIDwtIEZpbmRTcGF0aWFsbHlWYXJpYWJsZUZlYXR1cmVzKGJyZWFzdF9jYW5jZXIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFzc2F5ID0gIlNDVCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNsb3QgPSAic2NhbGUuZGF0YSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZlYXR1cmVzID0gVmFyaWFibGVGZWF0dXJlcyhicmVhc3RfY2FuY2VyKVsxOjEwMF0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VsZWN0aW9uLm1ldGhvZCA9ICJtYXJrdmFyaW9ncmFtIiwgdmVyYm9zZSA9IFRSVUUpCmBgYAoKSGF2ZSBhIGxvb2sgYXQgdGhlIHNwYXRpYWxseSB2YXJpYWJsZSBnZW5lcyBjYWxjdWxhdGVkIGJ5IGBtYXJrdmFyaW9ncmFtYCBvcmRlcmVkIGZyb20gbW9zdCB2YXJpYWJsZSB0byBsZWFzdCB2YXJpYWJsZQpgYGB7cn0KU3BhdGlhbGx5VmFyaWFibGVGZWF0dXJlcyhicmVhc3RfY2FuY2VyLCBzZWxlY3Rpb24ubWV0aG9kID0gIm1hcmt2YXJpb2dyYW0iLCBkZWNyZWFzaW5nID0gVFJVRSkKYGBgCgpgYGB7ciwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTgsIHdhcm5pbmc9RkFMU0V9CnRvcC5mZWF0dXJlc190cmVuZHNlcSA8LSBoZWFkKFNwYXRpYWxseVZhcmlhYmxlRmVhdHVyZXMoYnJlYXN0X2NhbmNlciwgc2VsZWN0aW9uLm1ldGhvZCA9ICJtYXJrdmFyaW9ncmFtIiksIDgpClNwYXRpYWxGZWF0dXJlUGxvdChicmVhc3RfY2FuY2VyLCBmZWF0dXJlcyA9IHRvcC5mZWF0dXJlc190cmVuZHNlcSwgbmNvbCA9IDQsIGFscGhhID0gYygwLjEsIDEpKQpgYGAKCk1vcmFuJ3MgSSBpbXBsZW1lbnRhdGlvbi4gRm9yIG90aGVyIHNwYXRpYWwgZGF0YSB0eXBlcyB0aGUgeC5jdXRzIGFuZCB5LmN1dHMgZGV0ZXJtaW5lcyB0aGUgZ3JpZCB0aGF0IGlzIGxhaWQgb3ZlciB0aGUgdGlzc3VlIGluIHRoZSBjYXB0dXJlIGFyZWEuIEhlcmUgd2UnbGwgcmVtb3ZlIHRob3NlCmBgYHtyfQpicmVhc3RfY2FuY2VyIDwtIEZpbmRTcGF0aWFsbHlWYXJpYWJsZUZlYXR1cmVzKGJyZWFzdF9jYW5jZXIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFzc2F5ID0gIlNDVCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNsb3QgPSAic2NhbGUuZGF0YSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZlYXR1cmVzID0gVmFyaWFibGVGZWF0dXJlcyhicmVhc3RfY2FuY2VyKVsxOjEwMF0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VsZWN0aW9uLm1ldGhvZCA9ICJtb3JhbnNpIikKYGBgCgoKSGF2ZSBhIGxvb2sgYXQgdGhlIHNwYXRpYWxseSB2YXJpYWJsZSBnZW5lcyBjYWxjdWxhdGVkIGJ5IGBtb3JhbnNpYCBvcmRlcmVkIGZyb20gbW9zdCB2YXJpYWJsZSB0byBsZWFzdCB2YXJpYWJsZQoKYGBge3J9ClNwYXRpYWxseVZhcmlhYmxlRmVhdHVyZXMoYnJlYXN0X2NhbmNlciwgc2VsZWN0aW9uLm1ldGhvZCA9ICJtb3JhbnNpIiwgZGVjcmVhc2luZyA9IFRSVUUpCmBgYAoKYGBge3IsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD04LCB3YXJuaW5nPUZBTFNFfQp0b3AuZmVhdHVyZXNfbW9yYW5zaSA8LSBoZWFkKFNwYXRpYWxseVZhcmlhYmxlRmVhdHVyZXMoYnJlYXN0X2NhbmNlciwgc2VsZWN0aW9uLm1ldGhvZCA9ICJtb3JhbnNpIiksIDgpClNwYXRpYWxGZWF0dXJlUGxvdChicmVhc3RfY2FuY2VyLCBmZWF0dXJlcyA9IHRvcC5mZWF0dXJlc19tb3JhbnNpLCBuY29sID0gNCwgYWxwaGEgPSBjKDAuMSwgMSkpCmBgYAoKV2UgY2FuIHNlZSB0aGF0IHRoZSByZXN1bHRzIGFyZSBzbGlnaHRseSBkaWZmZXJlbnQuIFNvIGxldCdzIHRha2UgYSBsb29rIGF0IHdoeS4KCmBgYHtyfQpzcGF0aWFsbHlfdmFyaWFibGVfZ2VuZXMgPC0gYnJlYXN0X2NhbmNlckBhc3NheXMkU0NUQG1ldGEuZmVhdHVyZXMgJT4lCiAgdGlkeXI6OmRyb3BfbmEoKQoKc3BhdGlhbGx5X3ZhcmlhYmxlX2dlbmVzCmBgYApZb3UgY2FuIHNlZSB0aGUgdHdvIG1ldGhvZHMgc2hvdyAKYGBge3J9Cm1tX2NvciA8LSBjb3IudGVzdChzcGF0aWFsbHlfdmFyaWFibGVfZ2VuZXMkbW9yYW5zaS5zcGF0aWFsbHkudmFyaWFibGUucmFuaywgc3BhdGlhbGx5X3ZhcmlhYmxlX2dlbmVzJG1hcmt2YXJpb2dyYW0uc3BhdGlhbGx5LnZhcmlhYmxlLnJhbmspCmdncGxvdChzcGF0aWFsbHlfdmFyaWFibGVfZ2VuZXMsIGFlcyh4PW1vcmFuc2kuc3BhdGlhbGx5LnZhcmlhYmxlLnJhbmsseT1tYXJrdmFyaW9ncmFtLnNwYXRpYWxseS52YXJpYWJsZS5yYW5rKSkrCiAgZ2VvbV9wb2ludCgpKwogIGdlb21fc21vb3RoKCkrCiAgeGxhYigiTW9yYW5zIEkgUmFuayIpKwogIHlsYWIoIk1hcmt2YXJpb2dyYW0gUmFuayIpKwogIGFubm90YXRlKCJ0ZXh0IiwgeCA9IDI1LCB5ID0gNzUsIGxhYmVsID0gcGFzdGUoIlBlYXJzb24ncyBDb3JyZWxhdGlvblxuIiwgcm91bmQobW1fY29yJGVzdGltYXRlWzFdLCBkaWdpdHMgPSAyKSwgc2VwID0gIiIpKSsKICB0aGVtZV9idygpCmBgYAoKIyMgQ2FuY2VyIGFubm90YXRpb25zCgpXaGVyZSBhcmUgdGhlc2UgZ2VuZXMgYmVpbmcgZXhwcmVzc2VkIHJlbGF0aXZlIHRvIHBhdGhvbG9naXN0IGFubm90YXRpb24/CgpgYGB7ciwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9OH0KY2EgPC0gcmVhZGJpdG1hcDo6cmVhZC5iaXRtYXAoImltYWdlcy9CcmVhc3QgQ2FuY2VyIFBhdGgucG5nIikKIyBpbiB0aGUgdHV0b3JpYWwKIyBjYSA8LSByZWFkYml0bWFwOjpyZWFkLmJpdG1hcCgnL21udC9saWJzL3NoYXJlZF9kYXRhL2h1bWFuX2JyZWFzdF9jYW5jZXJfMS9pbWFnZXMvQnJlYXN0X0NhbmNlcl9QYXRoLnBuZycpCnBsb3QoMDoxLDA6MSx0eXBlPSJuIixhbm49RkFMU0UsYXhlcz1GQUxTRSkKcmFzdGVySW1hZ2UoY2EsMCwwLDEsMSkKYGBgCgpMb29rcyBsaWtlIHRoZSAgTWF0cml4IEdsYSBwcm90ZWluICggX01HUF8gKSBnZW5lIGlzIGVucmljaGVkIGluIER1Y3RhbCBDYXJjaW5vbWEgX0luIFNpdHVfLiBOb3QgYSBsb3QgaXMga25vd24gYWJvdXQgX01HUF8gaW4gdGhlIGNvbnRleHQgb2YgY2FuY2VyIGJ1dCBpdCBsb29rcyBsaWtlIGl0IGNvdWxkIGJlIGFuIGludGVyZXN0aW5nIG5vdmVsIGdlbmUgdG8gaW52ZXN0aWdhdGUgd2l0aCByZWdhcmQgdG8gRHVjdGFsIENhcmNpbm9tYSBfSW4gU2l0dV8uCgpgYGB7ciwgd2FybmluZz1GQUxTRX0KU3BhdGlhbEZlYXR1cmVQbG90KG9iamVjdCA9IGJyZWFzdF9jYW5jZXIsIGZlYXR1cmVzID0gIk1HUCIsIGFscGhhID0gYygwLjEsIDEpLCBuY29sID0gMykKYGBgCg==